19  Seaborn库基础

19.1 引言Seaborn的优势

Seaborn建立在Matplotlib之上:

  • 更美观: 默认样式更好
  • 更简洁: 更少代码实现复杂图表
  • 统计图表: 专为统计设计

19.2 本章学习目标

通过本章学习,你将能够:

  1. 说出 Seaborn 与 Matplotlib 的关系:前者是构建在后者之上的统计图表高层接口,配置最终仍落到 plt.rcParams 与 Axes 上
  2. 区分图形级函数(catplot,自建画布、返回 FacetGrid)与坐标轴级函数(barplotboxplotregplot 等,可用 ax= 嵌入子图)
  3. sns.load_dataset() 加载内置数据集,完成分组统计图并解读误差线(95% 置信区间)
  4. set_style()set_palette() 设置全局样式与配色,并说明其作用范围
  5. heatmap 可视化相关系数矩阵,为第21章多维相关性分析做铺垫

先修:章节 18章节 10(第18章 Matplotlib 基础与第10章 Pandas 数据框:Seaborn 的绘图数据以 DataFrame 为主,图形要素仍用 Matplotlib 概念理解)

19.3 泰坦尼克数据集分析

任务要求(归纳自块内任务注释与代码):加载 Seaborn 内置的 titanic 数据集;用 sns.catplot 绘制以 class 为 x 轴、survived 为 y 轴、按 sex 分色的分组柱状图(kind="bar");调整子图顶部边距(subplots_adjust(top=0.9))后为整幅图添加总标题 "Survival rate by class and sex";将图形保存为 1.png。请将代码原样输入教学平台(注释除外),判定以平台为准。

列表 19.1: 平台原始代码
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import seaborn as sns  # 导入Seaborn可视化库
import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
# 加载titanic数据集
titanic = sns.load_dataset("titanic")
# 使用catplot函数绘制柱状图
sns.catplot(x="class", y="survived", hue="sex", data=titanic, kind="bar")
# 设置标题
plt.subplots_adjust(top=0.9)
plt.suptitle("Survival rate by class and sex")  # 设置图形总标题
# 显示图形
plt.savefig("1.png")

预期输出(本机 Python 实际运行结果,具体以平台运行结果为准):控制台无输出;生成文件 1.png,为一张按舱位等级分面、按性别分色的分组柱状图,共 6 根柱子,每根柱顶带一根黑色误差线(95% 置信区间)。数据集共 891 条乘客记录,各柱高度(生还率,保留两位小数)为:一等舱女性 0.97、男性 0.37;二等舱女性 0.92、男性 0.16;三等舱女性 0.50、男性 0.14——女性生还率系统性高于男性,且随舱位等级下降而下降,三等舱男性生还率最低。本机实跑已验证上述数值;load_dataset 首次运行需联网下载数据集。

19.4 常用Seaborn图表

列表 19.2
# =============================================================================
# 题目:Seaborn四种常用统计图表的综合演示
# =============================================================================
# 本代码展示Seaborn的核心图表类型:箱线图(展示分布和异常值)、小提琴图
# (结合箱线图和核密度估计)、热力图(展示相关性矩阵)和散点图带回归线
# (展示变量关系)。这些图表是数据分析和统计建模中的基础工具。

# ==================== 导入必要的库 ====================
import numpy as np  # 导入NumPy库,用于生成随机数
import pandas as pd  # 导入Pandas库,用于数据处理

# ==================== 创建示例数据 ====================
# 设置随机种子,确保每次运行结果相同
np.random.seed(42)  # 42是常用的随机种子,保证代码可重复

# 创建包含100行数据的DataFrame
data = pd.DataFrame({
    # Category列:从A、B、C三个类别中随机抽取100次,生成分类变量
    'Category': np.random.choice(['A', 'B', 'C'], 100),
    # Value1列:生成100个服从标准正态分布的随机数(均值0,标准差1)
    'Value1': np.random.randn(100),
    # Value2列:生成100个标准正态分布随机数后加2,使其均值变为2
    'Value2': np.random.randn(100) + 2  # 这使得Value2整体大于Value1
})

# ==================== 创建2x2的子图布局 ====================
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 创建画布和子图数组,总尺寸12x10英寸

# ==================== 子图1:箱线图 ====================
# 箱线图展示数据的分布特征:中位数、四分位数、异常值
sns.boxplot(data=data, x='Category', y='Value1', ax=axes[0, 0])
# x参数指定分类变量(横轴)
# y参数指定连续变量(纵轴)
# 箱线图从下到上依次表示:最小值、Q1(25%)、中位数、Q3(75%)、最大值
# 超出1.5倍IQR(四分位距)的点会被标记为异常值

# 设置子图1标题
axes[0, 0].set_title('箱线图')  # 箱线图能快速识别数据分布和异常值

# ==================== 子图2:小提琴图 ====================
# 小提琴图是箱线图和核密度图的结合,更直观地展示分布形状
sns.violinplot(data=data, x='Category', y='Value1', ax=axes[0, 1])
# 参数含义与boxplot相同,但视觉效果更丰富
# 小提琴的宽度表示数据在该值的密度,越宽表示出现频率越高

# 设置子图2标题
axes[0, 1].set_title('小提琴图')  # 能看到分布的对称性、峰度等特征

# ==================== 子图3:热力图 ====================
# 计算Value1和Value2的相关系数矩阵
corr = data[['Value1', 'Value2']].corr()
# corr()方法计算皮尔逊相关系数,衡量两个变量的线性相关程度
# 返回一个2x2的对称矩阵,对角线为1(自相关),非对角线为互相关

# 使用热力图可视化相关系数矩阵
sns.heatmap(corr, annot=True, cmap='coolwarm', ax=axes[1, 0])
# annot=True表示在热力图的每个格子上显示数值
# cmap='coolwarm'指定颜色方案(冷色表示负相关,暖色表示正相关)
# 热力图用颜色深浅表示相关系数大小,一目了然

# 设置子图3标题
axes[1, 0].set_title('相关性热力图')  # 展示变量间的线性关系强度

# ==================== 子图4:散点图带回归线 ====================
# regplot结合散点图和线性回归拟合,展示变量关系和趋势
sns.regplot(data=data, x='Value1', y='Value2', ax=axes[1, 1])
# x参数指定自变量(横轴)
# y参数指定因变量(纵轴)
# 自动计算并绘制线性回归线,以及95%置信区间(半透明阴影)

# 设置子图4标题
axes[1, 1].set_title('散点图与回归线')  # 直观判断变量关系和拟合优度

# ==================== 调整布局并显示 ====================
plt.tight_layout()  # 自动调整子图间距
plt.show()  # 显示完整的四合一图表

# 输出解读:
# 图1的箱线图展示了三个类别数据的分布特征和异常值
# 图2的小提琴图更细致地显示了分布形状,可能发现多峰分布等特征
# 图3的热力图显示Value1和Value2的相关系数(接近0表示无相关)
# 图4的散点图和回归线直观展示了两个变量的线性关系强度

19.5 样式设置

列表 19.3
# =============================================================================
# 题目:Seaborn样式和配色方案的自定义
# =============================================================================
# 本代码演示如何设置Seaborn的全局样式和配色方案,使图表更美观、更符合
# 个性化需求。Seaborn提供了5种内置样式和多种配色方案,适合不同的场景
# 和数据类型。

# ==================== 设置Seaborn样式 ====================
# set_style()设置图表的整体风格,影响背景、网格、坐标轴等元素
sns.set_style("whitegrid")  # 使用白色背景+灰色网格,适合大多数数据可视化场景
# 可选样式:
# "whitegrid": 白色背景,带网格(推荐用于数据展示)
# "darkgrid": 深色背景,带网格(适合深色主题)
# "white": 纯白背景,无网格(简洁风格)
# "dark": 深色背景,无网格(护眼模式)
# "ticks": 白色背景,仅保留刻度线(学术风格)

# ==================== 设置配色方案 ====================
# set_palette()设置图表的颜色循环顺序,影响所有使用颜色的图表元素
sns.set_palette("husl")  # 使用HUSL配色方案,颜色鲜艳且区分度高
# 常用配色方案:
# "husl": 均匀分布的色彩空间,适合分类数据
# "pastel": 柔和的粉彩色系
# "dark": 深色调
# "colorblind": 色盲友好的配色
# "Set1", "Set2": matplotlib的内置配色

# ==================== 创建散点图 ====================
# 创建一个新的画布,尺寸为10x6英寸
plt.figure(figsize=(10, 6))  # 单独创建画布,不使用子图

# 加载Seaborn内置的小费数据集(餐厅顾客的小费记录)
data_plot = sns.load_dataset('tips')
# 该数据集包含total_bill(总账单)、tip(小费)、day(星期)、size(人数)等字段

# 绘制多维散点图,通过颜色和大小区分不同维度
sns.scatterplot(data=data_plot, x='total_bill', y='tip', hue='day', size='size')
# x参数指定横轴变量(总账单金额)
# y参数指定纵轴变量(小费金额)
# hue参数指定按星期着色,不同星期用不同颜色表示
# size参数指定按人数映射点的大小,人数越多点越大

# ==================== 设置图表标签和标题 ====================
plt.title('餐厅小费分析', fontsize=14)  # 设置图表标题,字体大小14
plt.xlabel('总账单', fontsize=12)  # 设置横轴标签,字体大小12
plt.ylabel('小费', fontsize=12)  # 设置纵轴标签,字体大小12

# 设置图例
plt.legend(title='星期')  # 图例标题设为"星期",显示每天的标识

# ==================== 显示图表 ====================
plt.show()  # 展示完整的散点图

# 输出解读:
# 散点图展示了总账单与小费金额的正相关关系
# - 不同颜色(星期)的点的分布可能有差异,暗示周末小费比例不同
# - 点的大小(用餐人数)也影响小费金额,人数多的小费绝对值更高
# - 使用whitegrid样式和husl配色,图表清晰美观,便于识别模式和异常值

19.6 本章小结

要点:

  • Seaborn 建立在 Matplotlib 之上,用更少的代码得到更美观的统计图表;它接受整表数据(data= 传 DataFrame、x=/y=/hue= 传列名),这是与 Matplotlib 逐条传数据最大的用法差异
  • 函数分两级:图形级(如 catplot)自己创建画布、返回 FacetGrid 对象,适合快速分面;坐标轴级(如 boxplotviolinplotheatmapregplotscatterplot)接受 ax= 参数,可与 plt.subplots 组合嵌入子图
  • catplot(kind="bar") 自动完成分组均值统计,柱高是各组的 y 均值,并附带 95% 置信区间误差线——它画的是统计量,不是原始数据
  • set_style() 管背景与网格(whitegrid/darkgrid/white/dark/ticks),set_palette() 管配色;两者都是全局设置,会波及之后的所有图
  • sns.heatmap(corr, annot=True, cmap=...) 是相关系数矩阵的标准可视化方式,是第21章热力图任务的基础

易错点:

  • 图形级函数(如 catplot)没有 ax= 参数,无法嵌入已有子图;要嵌入须改用坐标轴级等价函数(如 sns.barplot)
  • set_style/set_palette 是全局的,忘记这一点会让后面的 Matplotlib 图也被”顺带”改了样式
  • 平台代码的 plt.suptitle 设置的是整幅图的总标题,与子图标题(set_title)不在一个层级,需要 subplots_adjust(top=0.9) 留出空间,否则会与子图重叠
  • load_dataset 的数据集名写错或断网时下载失败会报错;首次下载后有本地缓存,之后离线可用
  • 柱状图的误差线是置信区间,长短同时取决于样本量与该组比例的方差 \(p(1-p)\)(\(p\) 越接近 0.5 方差越大),样本多的组误差线未必更短,解读时不能只看柱高

19.7 动手与思考

以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。

  1. 输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。

    import seaborn as sns
    import matplotlib.pyplot as plt
    titanic = sns.load_dataset("titanic")
    g = sns.catplot(x="class", y="survived", data=titanic, kind="bar")
    print(type(g).__name__)
    print(len(g.fig.axes))
    fig, ax = plt.subplots()
    sns.boxplot(x="class", y="age", data=titanic, ax=ax)
    print(type(ax).__name__)

    参考答案(先写下你的预测再点开)

    解题思路:逐行推演。sns.catplot 是图形级函数,自己创建画布并返回 FacetGrid 对象,故第 1 个 print 输出 FacetGrid;本题没有 col/row 分面,整幅图只有 1 个坐标轴,g.fig.axes 是该图全部坐标轴的列表,故第 2 个 print 输出 1sns.boxplot 是坐标轴级函数,接受 ax= 参数嵌入指定坐标轴;plt.subplots() 不带行列参数时返回单个 Axes 对象,第 3 个 print 输出其类型名。注意版本差异:matplotlib 3.8 起 type(ax).__name__ 输出 Axes,旧版本(如 3.5)输出 AxesSubplot,以你所用版本的实际输出为准;本机 peter 环境为 matplotlib 3.10,输出 Axes。此外 sns.load_dataset("titanic") 首次运行需联网下载数据集,之后有本地缓存可离线使用。

    # 验证脚本:逐行输出三个表达式的求值结果(titanic首次加载需联网)
    import seaborn as sns  # 导入Seaborn可视化库
    import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
    titanic = sns.load_dataset("titanic")  # 加载Seaborn内置数据集
    g = sns.catplot(x="class", y="survived", data=titanic, kind="bar")  # 图形级函数:自建画布
    print(type(g).__name__)  # catplot返回对象的类型名
    print(len(g.fig.axes))  # FacetGrid画布上坐标轴的个数
    fig, ax = plt.subplots()  # 单个子图:返回单个Axes对象而非数组
    sns.boxplot(x="class", y="age", data=titanic, ax=ax)  # 坐标轴级函数:嵌入指定坐标轴
    print(type(ax).__name__)  # 该坐标轴对象的类型名

    预期输出(本机 peter 环境实际运行结果,titanic 首次加载需联网,具体以平台运行结果为准):

    FacetGrid
    1
    Axes

    回扣本章:对应本章小结“要点”第 2 条与“易错点”第 1 条——图形级函数(catplot)自建画布、返回 FacetGrid 且没有 ax= 参数;坐标轴级函数(如 boxplot)接受 ax= 可嵌入子图。

  2. 概念辨析:图形级函数与坐标轴级函数各适合什么场景?想在一张 1×2 的子图里并排画两张 Seaborn 图,应选哪一级、为什么?(catplot 能否通过 ax= 传入?可用 inspect.signature 检查。)

    参考答案(点开前请先独立完成)

    解题思路:图形级函数(catplotrelplotdisplot 等)自己创建画布并返回 FacetGrid,适合快速按 col/row 分面画一排图;坐标轴级函数(barplotboxplotviolinplotheatmapregplotscatterplot 等)接受 ax= 参数,可与 plt.subplots 组合嵌入指定子图。想在一张 1×2 的子图里并排画两张 Seaborn 图,应选坐标轴级函数:先 fig, axes = plt.subplots(1, 2),再分别传 ax=axes[0]ax=axes[1],两图共享同一画布便于统一尺寸与导出。catplot 不能通过 ax= 传入——其函数签名中没有 ax 参数(可用 inspect.signature 查证,下方代码输出 False),它必须自建画布;要嵌入已有子图须改用坐标轴级等价函数(如 sns.barplot)。

    # 验证脚本:检查两级函数的参数签名中是否含ax
    import inspect  # 导入检查函数签名的标准库模块
    import seaborn as sns  # 导入Seaborn可视化库
    print('catplot 有 ax 参数:', 'ax' in inspect.signature(sns.catplot).parameters)  # 图形级函数签名检查
    print('barplot 有 ax 参数:', 'ax' in inspect.signature(sns.barplot).parameters)  # 坐标轴级函数签名检查
    print('boxplot 有 ax 参数:', 'ax' in inspect.signature(sns.boxplot).parameters)  # 坐标轴级函数签名检查

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    catplot 有 ax 参数: False
    barplot 有 ax 参数: True
    boxplot 有 ax 参数: True

    回扣本章:对应本章小结“要点”第 2 条与“易错点”第 1 条——图形级函数没有 ax= 参数、无法嵌入已有子图,要嵌入须改用坐标轴级等价函数。

  3. 变式任务(平台任务同型改造):把平台任务的 catplot(kind="bar") 改为坐标轴级函数 sns.barplot 绘制同样的 class×sex 生还率图并嵌入 plt.subplots 创建的坐标轴;再画一张 embark_town×sex 的生还率图与之并排,对比两张图的样本量(误差线长短)差异。

    参考答案(点开前请先独立完成)

    解题思路:平台任务(列表 19.1)用的是图形级 catplot(kind="bar") 自建画布;变式改用坐标轴级 sns.barplot,先 plt.subplots(1, 2) 创建两个坐标轴,再分别以 ax=axes[0]ax=axes[1] 嵌入 class×sexembark_town×sex 两张生还率图。注意 embark_town 有 2 个缺失值,画图时按成对完备样本自动剔除,该图各组人数按 889 人分配。误差线对比的读法:embark_town 图各分组样本普遍更小(36—441 人),其中昆士敦(Queenstown)女性组仅 36 人、误差线最长;南安普敦(Southampton)男性 441 人、误差线最短;class 图中三等舱男性 347 人的误差线也很短——可见误差线长短由样本量与组内方差共同决定,样本多的组误差线未必一定更短,读图不能只看柱高。

    # 变式程序:用坐标轴级barplot把两张生还率图嵌入并排子图
    import seaborn as sns  # 导入Seaborn可视化库
    import matplotlib.pyplot as plt  # 导入Matplotlib绘图库
    titanic_df = sns.load_dataset('titanic')  # 加载Seaborn内置数据集(首次运行需联网)
    fig, axes = plt.subplots(1, 2, figsize=(12, 4))  # 创建1行2列子图
    sns.barplot(x='class', y='survived', hue='sex', data=titanic_df, ax=axes[0])  # 舱位×性别生还率嵌入左子图
    axes[0].set_title('class × sex 生还率')  # 左子图标题
    sns.barplot(x='embark_town', y='survived', hue='sex', data=titanic_df, ax=axes[1])  # 登船港口×性别生还率嵌入右子图
    axes[1].set_title('embark_town × sex 生还率')  # 右子图标题
    plt.tight_layout()  # 自动调整布局
    plt.savefig('ch19_q3.png')  # 保存图形(也可用plt.show()显示)
    class_table = titanic_df.groupby(['class', 'sex'], observed=True)['survived'].agg(['count', 'mean']).round(3)  # 左图各组人数与生还率
    town_table = titanic_df.groupby(['embark_town', 'sex'], observed=True)['survived'].agg(['count', 'mean']).round(3)  # 右图各组人数与生还率
    print('class × sex 各组人数与生还率:')  # 打印左图底表
    print(class_table)  # 输出左图底表
    print('embark_town × sex 各组人数与生还率:')  # 打印右图底表
    print(town_table)  # 输出右图底表

    预期输出(本机 peter 环境实际运行结果,titanic 首次加载需联网,图形在线版不执行、以教学平台渲染为准):

    class × sex 各组人数与生还率:
                   count   mean
    class  sex                 
    First  female     94  0.968
           male      122  0.369
    Second female     76  0.921
           male      108  0.157
    Third  female    144  0.500
           male      347  0.135
    embark_town × sex 各组人数与生还率:
                        count   mean
    embark_town sex                 
    Cherbourg   female     73  0.877
                male       95  0.305
    Queenstown  female     36  0.750
                male       41  0.073
    Southampton female    203  0.690
                male      441  0.175

    图形判读:两张图各 6 根柱(3 个分组 × 2 个性别),每根柱顶带一根黑色 95% 置信区间误差线;本机固定随机种子的自助抽样实测,右图昆士敦女性组(36 人)误差线半长约 0.14、最长,南安普敦男性组(441 人)半长约 0.03、最短(自助抽样每次运行略有波动,量级不变)。

    注意:以上为本题变式的独立代码;列表 19.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。

    回扣本章:对应本章小结“要点”第 2、3 条(坐标轴级函数接受 ax=kind="bar" 的柱高是分组均值并附 95% 置信区间误差线)与“易错点”第 5 条(误差线长短同时取决于样本量与组内方差)。

  4. 思考题:柱高是各组生还率的均值,误差线是 95% 置信区间。一等舱男性乘客(122 人)比一等舱女性(94 人)样本更多,但其柱子的误差线反而明显更长。为什么?提示:生还率这类比例变量的方差为 \(p(1-p)\),在 \(p=0.5\) 附近最大。这对”读图下结论”有什么提醒?

    参考答案(点开前请先独立完成)

    解题思路:生还率是比例变量,某组比例估计的标准误为 \(\sqrt{p(1-p)/n}\),其中 \(p(1-p)\) 是比例的方差、在 \(p=0.5\) 附近最大、越接近 0 或 1 越小。一等舱男性 \(p\approx 0.369\)\(p(1-p)\approx 0.233\);一等舱女性 \(p\approx 0.968\)\(p(1-p)\approx 0.031\)——两组方差相差约 7.5 倍,而样本量只差约 30%(122 人对 94 人),标准误相除约得 2.4 倍,所以样本更多的男性组误差线反而明显更长。对“读图下结论”的提醒:柱高接近 0 或 1 的组区间天然短、接近 0.5 的组天然长,误差线长短不能直接当“样本多不多”或“结论可不可靠”读;报告分组比例时应同时给出样本量与置信区间,差异是否显著要看区间是否重叠、必要时做正式的统计检验,而不是只比柱高。

    # 验证脚本:一等舱分性别的人数、生还率与比例标准误
    import numpy as np  # 导入NumPy用于开方运算
    import seaborn as sns  # 导入Seaborn用于加载内置数据集
    titanic_df = sns.load_dataset('titanic')  # 加载泰坦尼克数据集(首次运行需联网)
    first_class_df = titanic_df[titanic_df['class'] == 'First']  # 取一等舱乘客子集
    group_stat = first_class_df.groupby('sex', observed=True)['survived'].agg(['count', 'mean'])  # 各性别人数与生还率
    group_stat['std_error'] = np.sqrt(group_stat['mean'] * (1 - group_stat['mean']) / group_stat['count'])  # 比例标准误sqrt(p(1-p)/n)
    print(group_stat.round(3))  # 保留三位小数输出
    print('男性与女性标准误之比: {:.2f}'.format(group_stat.loc['male', 'std_error'] / group_stat.loc['female', 'std_error']))  # 两组误差线相对长短

    预期输出(本机 peter 环境实际运行结果,titanic 首次加载需联网,具体以平台运行结果为准):

            count   mean  std_error
    sex
    female     94  0.968      0.018
    male      122  0.369      0.044
    男性与女性标准误之比: 2.41

    本机固定随机种子的实测中,平台任务同款 catplot 图上一等舱男性误差线半长约 0.086、女性约 0.032,与正态近似 \(1.96\times\) 标准误(0.086 与 0.035)基本一致(自助抽样每次运行略有波动)。

    回扣本章:对应本章小结“易错点”第 5 条——柱状图误差线是置信区间,长短同时取决于样本量与该组比例的方差 \(p(1-p)\),样本多的组误差线未必更短,解读时不能只看柱高。